/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package grex;

import java.util.ArrayList;
import java.util.HashMap;

/**
 *
 * @author RIK
 */
public class PredictiveEnsemble implements IPredictiveModel {

    private ArrayList<IPredictiveModel> baseModels;
    PredictionContainer pcTrain, pcTest, pcVal;
    private int problemType = Options.CLASSIFICATION;
    private double[][] orgTest, orgTrain, orgVal;
    private boolean testInitialized = false, trainInitialized = false, vaLinitialized = false;;
    int targetCategories;
    Environment env;
    public PredictiveEnsemble(Environment env) {
        this.baseModels = new ArrayList<IPredictiveModel>();
        this.problemType = env.getOptions().getPROBLEM_TYPE();
        orgTest = env.getTestData();
        orgTrain = env.getTrainData();
        orgVal = env.getValData();
        this.env = env;
        initPredictionContainers();
        targetCategories = env.getNrOfTargetCategories();

    }

    public void addPredictiveBaseModel(IPredictiveModel baseModel) {
        baseModels.add(baseModel);
    }
    
    public void initFold(String fold){
        env.getData().initFold(fold);
    }

    private void initPredictionContainers() {
        pcTrain = new PredictionContainer(orgTrain.length);
        pcTest = new PredictionContainer(orgTest.length);
        pcVal = new PredictionContainer(orgVal.length);
        for (double[] instance : orgTrain) {
            pcTrain.put(instance, new Prediction(instance));
        }

        for (double[] instance : orgTest) {
            pcTest.put(instance, new Prediction(instance));
        }
        
         for (double[] instance : orgVal) {
            pcVal.put(instance, new Prediction(instance));
        }
    }

    public PredictionContainer getPcTrain() {
        if (!trainInitialized) {
            execute(pcTrain);
            trainInitialized = true;
        }

        return pcTrain;
    }

    public PredictionContainer getPcTest() {
        if (!testInitialized) {
            execute(pcTest);
            testInitialized = true;
        }
        return pcTest;
    }
    
   public PredictionContainer getPcVal() {
        if (!vaLinitialized) {
            execute(pcVal);
            vaLinitialized = true;
        }
        return pcVal;
    }

    public String getOrderedTestPredictions() {
        String s = "Target\tPrediction\n";
        getPcTest();

        for (int i = 0; i < orgTest.length; i++) {
            double[] ds = orgTest[i];
            Prediction p = pcTest.get(ds);
            s += p.getTargetValue() + "\t" + p.getPrediction() + "\n";
        }

        return s;
    }

    public void train() {
        execute(pcTrain);
        trainInitialized = true;
    }

    public void execute(PredictionContainer pc) {


        ///Work is temporary PC and is used to sum all predictions. The orignal prediction i work is zero so adding make sense.
        if (problemType == Options.CLASSIFICATION) {
            executeClassification(pc);
        } else {
            executeRegression(pc);
        }

    }

    private void executeClassification(PredictionContainer pc) {
        HashMap<double[], double[]> allVotes = new HashMap<double[], double[]>();
        for (IPredictiveModel model : baseModels) {
            model.execute(pc);
            for (Prediction p : pc.values()) {
                double[] votes = allVotes.get(p.getInstance());
                if (votes == null) {
                    votes = new double[targetCategories];
                    allVotes.put(p.getInstance(), votes);
                }
                votes[(int) p.getPrediction()]++;
            }

        }
             for (Prediction p : pc.values()) {
                p.setProbsUsingSupport(allVotes.get(p.getInstance()));
                p.setLeaf(null);
                /*    int maxVote = -1, maxClass = -1;
                int[] votes = allVotes.get(p.getInstance());
                for (int c = 0; c < votes.length; c++) {
                    int vote = votes[c];
                    if (vote > maxVote) {
                        maxVote = vote;
                        maxClass = c;
                    }
                }
                p.setPrediction(maxClass);
                p.setLeaf(null);*/
            }
    }
    

    private void executeRegression(PredictionContainer pc) {
        PredictionContainer work = new PredictionContainer(pc.values().size());
        for (Prediction p : pc.values()) {
            work.put(p.getInstance(), new Prediction(p.getInstance()));
        }

        for (IPredictiveModel model : baseModels) {
            model.execute(pc);
            for (Prediction p : pc.values()) {
                Prediction wp = work.get(p.getInstance());
                double value = wp.getPrediction() + p.getPrediction() / baseModels.size();
                wp.setPrediction(value);
            }
        }

        for (Prediction p : work.values()) {
            Prediction realP = pc.get(p.getInstance());
            realP.setPrediction(p.getPrediction());
            realP.setLeaf(null);
        }
    }

    public double getNrOfNodes() {
        int nodes = 0;
        for (IPredictiveModel model : baseModels) {
            nodes += model.getNrOfNodes();
        }
        return nodes;
    }

    public String getName() {
        String name = "Ens";
        if (!baseModels.isEmpty()) {
            name += baseModels.get(0).getName();
        }
        return name;
    }

    public double[] calcSensitivity() {
        throw new UnsupportedOperationException("Not supported yet in class PredicitiveEnsemble");
    }
}
